/***********************************************************************
 * 
 *  This file is a part of Minesweeper - a computer game
 *  Copyright (C) 2020, 2021 Andrew Tokarskiy <tokarskiy.a@keemail.me>
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>
 * 
*************************************************************************/

#include "mines.h"
#include <stdlib.h>

#define ALLOCATE(type, amount) (type*)malloc(sizeof(type) * amount)
 
void randomizeMines(Game* const game, const size_t firstMoveI, const size_t firstMoveJ, const int minesAmount); 

Game* initGame(const size_t height, const size_t width, const int minesAmount) {
    int** map = ALLOCATE(int*, height);
    int** statuses = ALLOCATE(int*, height); 
    for (int i = 0; i < height; i++) {
        map[i] = ALLOCATE(int, width);
        statuses[i] = ALLOCATE(int, width); 
        for (int j = 0; j < width; j++) {
            map[i][j] = 0; 
            statuses[i][j] = STATUS_NULL; 
        }
    }

    Game* game = ALLOCATE(Game, 1); 
    game->map = map;
    game->statuses = statuses; 
    game->width = width; 
    game->height = height; 
    game->minesAmount = minesAmount; 
    game->initialized = 0; 

    return game; 
}

const size_t sqr(const size_t a) {
    return a * a; 
}

const size_t sqrdif(const size_t a, const size_t b) {
    return a > b ? sqr(a - b) : sqr(b - a); 
}

const size_t decIndex(const size_t a) {
    return a == 0 ? a : a - 1;
}

const size_t incIndex(const size_t a, const size_t max) {
    return a + 1 >= max ? a : a + 1;
}

void shuffle(size_t* array, const size_t count) {
    if (count > 1) {
        for (size_t i = 0; i < count - 1; i++) {
            const size_t j = i + rand() / (RAND_MAX / (count - i) + 1);
            const int t = array[j];
            array[j] = array[i];
            array[i] = t;
        }
    }
}

const int calculateMinesAround(Game* const game, const size_t i, const size_t j) {
    int amount = 0; 

    const size_t minII = decIndex(i);
    const size_t maxII = incIndex(i, game->height);
    const size_t minJJ = decIndex(j);
    const size_t maxJJ = incIndex(j, game->width); 

    for (size_t ii = minII; ii <= maxII; ii++) {
        for (size_t jj = minJJ; jj <= maxJJ; jj++) {
            if (ii == i && jj == j) {
                continue;
            }

            if (game->map[ii][jj] == MINE_INDEX) {
                amount++; 
            }
        }
    }

    return amount; 
}

void randomizeMines(Game* const game, const size_t firstMoveI, const size_t firstMoveJ, const int minesAmount) {
    int** map = game->map; 

    const size_t size = game->width * game->height;
    size_t* possiblePlaces = ALLOCATE(size_t, size); 
    size_t pos = 0; 
    for (size_t i = 0; i < size; i++) {
        const size_t posI = i / game->width; 
        const size_t posJ = i % game->width; 

        const size_t sqDistance = sqrdif(firstMoveI, posI) + sqrdif(firstMoveJ, posJ); 
        if (sqDistance >= 4) { // 2 - distance between points (0, 0) & (2, 0). sqrt(2) - distance between points (0, 0) & (1, 1)
            possiblePlaces[pos] = i; 
            pos++;
        } 
    }

    const size_t possiblePlacesSize = pos; 
    shuffle(possiblePlaces, possiblePlacesSize); 
    for (int i = 0; i < minesAmount; i++) {
        const size_t place = possiblePlaces[i]; 
        const size_t posI = place / game->width;
        const size_t posJ = place % game->width; 
        
        map[posI][posJ] = MINE_INDEX; 
    }

    free(possiblePlaces); 

    for (size_t i = 0; i < game->height; i++) {
        for (size_t j = 0; j < game->width; j++) {
            if (map[i][j] != MINE_INDEX) {
                map[i][j] = calculateMinesAround(game, i, j); 
            }
        }
    }
}

void freeGame(Game* const game) {
    int* line;
    size_t i; 
    
    for (i = 0; i < game->height; i++) {
        line = game->map[i]; 
        free(line); 
    }

    for (i = 0; i < game->height; i++) {
        line = game->statuses[i]; 
        free(line); 
    }

    free(game->map);
    free(game->statuses); 
    free(game); 
}

// call only when map[i][j] >= 0
void fill(Game* const game, const size_t i, const size_t j) {
    if (game->statuses[i][j] >= 0) {
        return; 
    } 

    game->statuses[i][j] = game->map[i][j];

    const size_t minII = decIndex(i);
    const size_t maxII = incIndex(i, game->height);
    const size_t minJJ = decIndex(j);
    const size_t maxJJ = incIndex(j, game->width); 

    if (game->map[i][j] == 0) {
        for (size_t ii = minII; ii <= maxII; ii++) {
            for (size_t jj = minJJ; jj <= maxJJ; jj++) {
                if (ii == i && jj == j) {
                    continue;
                }

                fill(game, ii, jj); 
            }
        }
    } 
}

const ActionResult checkWinStatus(Game* const game) {
    int** statuses = game->statuses; 
    int nullCount = 0; 
    
    for (size_t i = 0; i < game->height; i++) {
        for (size_t j = 0; j < game->width; j++) {
            if (statuses[i][j] == STATUS_FLAG || statuses[i][j] == STATUS_NULL) {
                nullCount++; 
            }
        }
    }

    return nullCount == game->minesAmount ? Win : Continue; 
}

// return 0 -> continue, return 1 -> lose, 2 -> win 
const ActionResult action(Game* const game, const size_t i, const size_t j, const ActionType actionType) {
    int** statuses = game->statuses;
    int** map = game->map; 
    if (statuses[i][j] >= 0) { // if cell is opened
        return Continue; 
    }

    if (actionType == MarkMine && statuses[i][j] == STATUS_NULL) {
        statuses[i][j] = STATUS_FLAG; 
        return Continue; 
    }

    if (actionType == MarkMine && statuses[i][j] == STATUS_FLAG) {
        statuses[i][j] = STATUS_NULL; 
        return Continue; 
    }

    if (game->initialized == 0) {
        randomizeMines(game, i, j, game->minesAmount);
        game->initialized = 1;  
    }

    if (game->map[i][j] == MINE_INDEX) {
        for (size_t ii = 0; ii < game->height; ii++) {
            for (size_t jj = 0; jj < game->width; jj++) {
                if (map[ii][jj] == MINE_INDEX) {
                    statuses[ii][jj] = STATUS_MINE; 
                }
            }
        }

        return Lose;
    }

    fill(game, i, j);
 
    return checkWinStatus(game); 
}

